﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

/* Copyright (c) 2014
 * author: Ricky Kratochwil
 * @ date: 2014-05-19
 * @ summary: This class handles all player related operations
*/

namespace Engine
{
	public class CPlayerCore
	{
		private CPlayer[]	m_rgoPlayer;
		private CWorld		m_oWorld;
		private Thread		m_oFreeFallThread;
		private int			m_iSectorHeight;

		private Label		m_lblP1Coins, m_lblP2Coins;
		private Label		m_lblP1Stamina, m_lblP2Stamina;

		public CPlayerCore()
		{
			m_rgoPlayer = new CPlayer[2];		//Allow for 2 players
		}

//////////////////////////////////////////////////////////////////////////////////////////////////////
////////// UTILITIES

		//Set world to player core for player <-> world transactions
		// - Calculate sector height
		//@ KR / 2014.05.20
		public void SetWorld(ref CWorldCore oWorldCore)
		{
			int	iWidth	= 0;// = Const.GRIDS_XAXIS*Const.GRID_DIM;
			int	iHeight = 0;// = Const.GRIDS_YAXIS*Const.GRID_DIM;
			oWorldCore.GetWorld(ref m_oWorld);

			m_oWorld.GetWorldDimensions(ref iWidth, ref iHeight);
			m_iSectorHeight = (int)((iHeight/Const.GRID_DIM) / 4);
		}

		//Set labels for automatic updates
		//@ KR / 2014.06.29
		public void SetLabel(Label lblP1Coins, Label lblP2Coins, Label lblP1Stamina, Label lblP2Stamina)
		{
			m_lblP1Coins	= lblP1Coins;
			m_lblP2Coins	= lblP2Coins;
			m_lblP1Stamina	= lblP1Stamina;
			m_lblP2Stamina	= lblP2Stamina;
		}

//////////////////////////////////////////////////////////////////////////////////////////////////////
////////// PLAYER CREATION

		//[DEPRECATED]
		//Start game
		//@ KR / 2014.05.19
		// - iPlayer is the 1 terminated ID
		// - Not maintained anymore, compatibility only
		public bool CreatePlayer(int iPlayer, int iPlayerTemplateID, int iX, int iY, bool bSuperman = false)
		{
			if(iPlayer >= 1 && iPlayer <= 2)
			{
				--iPlayer;

				m_rgoPlayer[iPlayer] = new CPlayer(iPlayerTemplateID, bSuperman);
				m_rgoPlayer[iPlayer].SetStartPos(iX, iY);

				return true;
			}
			
			return false;
		}

		//Create new Player
		//@ KR / 2014.06.25
		// - iPlayer is the 1 terminated ID
		public void CreatePlayer(int iPlayer, int iPlayerTemplateID, bool bSuperman = false)
		{
			if(iPlayer >= 1 && iPlayer <= 2)
			{
				--iPlayer;

				m_rgoPlayer[iPlayer] = new CPlayer(iPlayerTemplateID, bSuperman);
			}
		}


		//Set player start pos (mandatory)
		//@ KR / 2014.06.25 (revised)
		public void SetStartPos(int iPlayer, Point ptPlayerLoc)
		{
			m_rgoPlayer[--iPlayer].SetStartPos(ptPlayerLoc.X, ptPlayerLoc.Y);
		}

//////////////////////////////////////////////////////////////////////////////////////////////////////
////////// GAME LOGIC

		//Start game
		//@ KR / 2014.05.20
		// - Reset world
		// - Check for at least 2 player (Should be ensured before calling StartGame)
		// - Set players to start pos
		public bool StartGame()
		{
			if(m_oWorld != null)	//Only if world existst
			{
				int iX = 0, iY = 0;

				if(m_rgoPlayer[0] == null || m_rgoPlayer[1] == null)		//At least two players available
					return false;

				for(int i = 0; i < m_rgoPlayer.Length; i++)
				{
					m_rgoPlayer[i].GetStartPos(ref iX, ref iY);
					m_rgoPlayer[i].SetPos(iX, iY);

					m_rgoPlayer[i].RegAnimObj();
				}

				m_oWorld.SetRandom(true);		//Start random engine

				return true;
			}
			return false;
		}

		//End game [<<IRREVERSIBLE>>]
		//@ KR / 2014.06.24
		// - Stop animation
		public void EndGame()
		{
			if(m_oWorld != null)	//Only if world existst
			{
				m_oWorld.SetRandom(false);		//Stop random engine

				//Nullify player animation objects (to reset them for creation)
				for(int i = 0; i < m_rgoPlayer.Length; i++)
					m_rgoPlayer[i].ResetAnimObj();
			}
		}

		//Dispose all allocated resources for players
		//@ KR / 2014.06.29
		public void Dispose()
		{
			for(int i = 0; i < m_rgoPlayer.Length; i++)
			{
				m_rgoPlayer[i].Destroy();
			}
		}

		//Check if opponent is over current position
		//@ KR / 2014.06.29
		public bool OverOpponent(int iPlayer)
		{
			int iPlayerX = 0, iPlayerY = 0;
			int iOppX	 = 0, iOppY	   = 0;
			iPlayer--;

			m_rgoPlayer[iPlayer].GetPos(ref iPlayerX, ref iPlayerY);
			m_rgoPlayer[iPlayer == 0 ? 1 : 0].GetPos(ref iOppX, ref iOppY);

			if((iPlayerX == iOppX) && (iPlayerY == iOppY))
				return true;
			else
				return false;
		}

		//Check if opponent is at provided position
		// - Internal use only!
		// - iPlayer musst be 0 based at this time
		//@ KR / 2014.07.07
		private bool HasOpponent(int iPlayer, int iX, int iY)
		{
			int iOppX	 = 0, iOppY	   = 0;

			m_rgoPlayer[iPlayer == 0 ? 1 : 0].GetPos(ref iOppX, ref iOppY);

			if((iOppX == iX) && (iOppY == iY))
				return true;
			else
				return false;
		}

//////////////////////////////////////////////////////////////////////////////////////////////////////
////////// PLAYER MOVEMENT

		//Move player right
		//@ KR / 2014.05.20
		public bool MoveRight(int iPlayer, bool bKicked = false)
		{
			int		iX = 0, iY = 0;
			int		iStaminaReq;
			CPlayer oPlayer = m_rgoPlayer[--iPlayer];

			oPlayer.GetPos(ref iX, ref iY);	//Get Pos

			//Movement
			if(!m_oWorld.IsSolid(iX+1, iY))			//Can mot walk through wall
			{
				//Stamina
				iStaminaReq = 1;	//Fixed stamina
				if(!bKicked && (oPlayer.GetStamina() != -1 && oPlayer.GetStamina() < iStaminaReq))	//Unlimited movement cheat is -1
					return false;

				if (oPlayer.MoveRight(iStaminaReq))	//Change player position if movement was possible	
					PostMovementAction(iPlayer);
				return true;
			}	
			return false;
		}

		//Move player left
		//@ KR / 2014.05.21
		public bool MoveLeft(int iPlayer, bool bKicked = false)
		{
			int		iX = 0, iY = 0;
			int		iStaminaReq;
			CPlayer oPlayer = m_rgoPlayer[--iPlayer];

			oPlayer.GetPos(ref iX, ref iY);	

			//Movement
			if(!m_oWorld.IsSolid(iX-1, iY))		//Can mot walk through wall
			{
				//Stamina
				iStaminaReq = 1;	//Fixed stamina
				if(!bKicked && (oPlayer.GetStamina() != -1 && oPlayer.GetStamina() < iStaminaReq))	//Unlimited movement cheat is -1
					return false;

				if(oPlayer.MoveLeft(iStaminaReq))		//Movement will only happen when possible
					PostMovementAction(iPlayer);
				return true;
			}
			return false;
		}

		//Move player up
		//@ KR / 2014.05.21
		public bool MoveUp(int iPlayer, bool bKicked = false)
		{
			int		iX = 0, iY = 0;
			int		iStaminaReq;
			CPlayer oPlayer = m_rgoPlayer[--iPlayer];

			oPlayer.GetPos(ref iX, ref iY);

			//Movement
			if(!m_oWorld.IsSolidCeiling(iX, iY))		//Can mot walk through ceiling
			{
				//Stamina
				iStaminaReq = GetRequiredStamina(iY-1);	//Get required stamina
				if(!bKicked && (oPlayer.GetStamina() != -1 && oPlayer.GetStamina() < iStaminaReq))	//Unlimited movement cheat is -1
					return false;

				if(oPlayer.MoveUp(iStaminaReq))		//Movement will only happen when possible
					PostMovementAction(iPlayer);
				return true;
			}
			return false;
		}

		//Start player free fall
		// - Generally enables free fall, does not check if current stand allow for this action
		// - Thread safe
		// - Ensures only one thread can exist at any given time
		// - Provide point label to invoke automatic update upon coin collection
		//@ KR / 2014.06.28 (revised)
		public void FreeFall(int iPlayer, bool bKicked)
		{
			if(m_oFreeFallThread == null || !m_oFreeFallThread.IsAlive)
			{
				m_oFreeFallThread = new Thread(() => FreeFallThread(--iPlayer, bKicked));
				m_oFreeFallThread.Start();
			}
		}

		//Free Fall Thread
		// - Makes player falling and collecting coins on the way
		//@ KR / 2014.06.28
		private void FreeFallThread(int iPlayer, bool bKicked)
		{
			int		iX = 0, iY = 0;
			int		iStaminaReq;
			CPlayer oPlayer = m_rgoPlayer[iPlayer];

			oPlayer.GetPos(ref iX, ref iY);

			do
			{
				iStaminaReq = GetRequiredStamina(iY+1);		//Get required stamina
				if(oPlayer.FreeFall(iStaminaReq, bKicked))
				{
					oPlayer.GetPos(ref iX, ref iY);

					PostMovementAction(iPlayer);
				}
			}
			while((!m_oWorld.IsSolidStand(iX, iY) || m_oWorld.IsSolid(iX, iY)) && (iY+1) < Const.GRIDS_YAXIS);

			//If player lands on spikes, remove 5 coins
			if(m_oWorld.IsOverSpikes(iX, iY))
			{
				oPlayer.RemovePoints(5);
				
				switch(iPlayer)
				{
					case 0 :
						if(m_lblP1Coins != null){m_lblP1Coins.Invoke((MethodInvoker)delegate{m_lblP1Coins.Text = oPlayer.GetPoints().ToString();});}
						break;
					case 1 :
						if(m_lblP2Coins != null){m_lblP2Coins.Invoke((MethodInvoker)delegate{m_lblP2Coins.Text = oPlayer.GetPoints().ToString();});}
						break;
				}
			}

			//Check if brick landed on is damaged. Remove if so
			if(m_oWorld.IsStandBrickDamaged(iX, iY))
			{
				m_oWorld.SetBrick(Const.BLOCK_ICE, iX, iY+1);
				CAnimationCore.UpdateWorld(m_oWorld.GetWorldBitmap());
			}

			//Will land on opponent
			if(OverOpponent(iPlayer+1))
			{
				oPlayer.IncKicks();
				if(iY+1 == Const.GRIDS_YAXIS)	//Opponent is standing on world edge, so move him either left or right
				{
					if(iX == 0)
					{
						if(!MoveRight(iPlayer == 0 ? 2 : 1, true))
							MoveUp(iPlayer == 0 ? 2 : 1, true);
					}
					else
					{
						if(!MoveLeft(iPlayer == 0 ? 2 : 1, true))
							MoveUp(iPlayer == 0 ? 2 : 1, true);
					}
				}
				else
					FreeFallThread(iPlayer == 0 ? 1 : 0, true);		//Kick player
			}
		}

		public void Kick(int iPlayer)
		{
			int iX = 0, iY = 0;
			CPlayer oPlayer = m_rgoPlayer[--iPlayer];
			oPlayer.GetPos(ref iX, ref iY);

			//Will land on opponent
			if(OverOpponent(iPlayer == 0 ? 1 : 2))
			{
				oPlayer.IncKicks();
				int iOppPlayer = iPlayer == 0 ? 2 : 1;

				if(iY+1 == Const.GRIDS_YAXIS)	//Opponent is standing on world edge, so move him either left or right
				{
					if(iX == 0)
					{
						if(!MoveRight(iOppPlayer, true))
							MoveUp(iOppPlayer, true);
					}
					else
					{
						if(!MoveLeft(iOppPlayer, true))
							MoveUp(iOppPlayer, true);
					}
				}
				else	
					FreeFall(iOppPlayer, true);		//Kick player
			}
		}

		//All needed action after movement completion in one function
		// - Implemented to reduce redundand code
		// - Invoke points update
		//@ KR / 2014.06.28
		private void PostMovementAction(int iPlayer)
		{
			int		iX = 0, iY = 0;
			CPlayer oPlayer = m_rgoPlayer[iPlayer];

			oPlayer.GetPos(ref iX, ref iY);
	
			CollectCoin(iPlayer, iX, iY);
			CollectPowerUps(iPlayer, iX, iY);

			//Automated stamina and coin output
			switch(iPlayer)
			{
				case 0 :
					if(m_lblP1Coins != null){m_lblP1Coins.Invoke((MethodInvoker)delegate{m_lblP1Coins.Text = oPlayer.GetPoints().ToString();});}
					if(m_lblP1Stamina != null){m_lblP1Stamina.Invoke((MethodInvoker)delegate{m_lblP1Stamina.Text = oPlayer.GetStamina().ToString();});}
					break;
				case 1 :
					if(m_lblP2Coins != null){m_lblP2Coins.Invoke((MethodInvoker)delegate{m_lblP2Coins.Text = oPlayer.GetPoints().ToString();});}
					if(m_lblP2Stamina != null){m_lblP2Stamina.Invoke((MethodInvoker)delegate{m_lblP2Stamina.Text = oPlayer.GetStamina().ToString();});}
					break;
			}
			

		}

		//Is player currently in animated condition?
		//@ KR / 2014.06.28
		public bool IsAnimated(int iPlayer)
		{
			--iPlayer;

			return m_rgoPlayer[iPlayer].IsAnimated();
		}

		//Return true if player is standing on solid ground
		//@ KR / 2014.06.28
		public bool HasSolidStand(int iPlayer)
		{
			int iX = 0, iY = 0;

			m_rgoPlayer[--iPlayer].GetPos(ref iX, ref iY);
			return m_oWorld.IsSolidStand(iX, iY);
		}

//////////////////////////////////////////////////////////////////////////////////////////////////////
////////// STAMINA

		//Generate Stamina and add to player
		// - Each player will recieve 20 stamina points per round
		//@ KR / 2014.06.28
		public void GenerateStamina(int iPlayer)
		{
			m_rgoPlayer[--iPlayer].AddStamina(20);
		}

		//Reset Stamina to 0 for player
		//@ KR / 2014.07.08
		public void ResetStamina(int iPlayer)
		{
			m_rgoPlayer[--iPlayer].SetStamina(0);
		}

		//Get player stamina
		//@ KR / 2014.06.25
		public int GetStamina(int iPlayer)
		{
			return m_rgoPlayer[--iPlayer].GetStamina();
		}

		//Calculate required stamina for movement in current world height
		//@ KR / 2014.06.28
		private int GetRequiredStamina(int iY)
		{
			int iStaminaReq = (int)(iY/m_iSectorHeight);
			return 4-iStaminaReq;
		}

//////////////////////////////////////////////////////////////////////////////////////////////////////
////////// POINTS

		//Get player points
		//@ KR / 2014.06.25
		public int GetPoints(int iPlayer)
		{
			return m_rgoPlayer[--iPlayer].GetPoints();
		}

		//Try collecting coin at position
		// - Implemented to reduce redundand code
		//@ KR / 2014.06.28
		private void CollectCoin(int iPlayer, int iX, int iY)
		{
			CCoin oCoin;

			//Check for coin on current pos, get value if existing and remove coin
			oCoin = m_oWorld.GetCoin(iX, iY);
			if(oCoin != null)
			{
				m_rgoPlayer[iPlayer].AddPoints(oCoin.GetCoins());
				m_oWorld.RemoveCoin(iX, iY);
			}
		}

//////////////////////////////////////////////////////////////////////////////////////////////////////
////////// POWER UPS

		//Try collecting power ups at position
		//@ KR / 2014.07.09
		private void CollectPowerUps(int iPlayer, int iX, int iY)
		{
			CPowerUp oPowerUp;

			//Check for coin on current pos, get value if existing and remove coin
			oPowerUp = m_oWorld.GetPowerUp(iX, iY);
			if(oPowerUp != null)
			{
				if(oPowerUp.GetType() == typeof(CPowerUpDiamond))
					m_rgoPlayer[iPlayer].AddPoints(oPowerUp.GetValue());
				else if(oPowerUp.GetType() == typeof(CPowerUpStamina))
					m_rgoPlayer[iPlayer].AddStamina(oPowerUp.GetValue());
				else if(oPowerUp.GetType() == typeof(CPowerUpTeleport))			
					Beam(iPlayer);

				m_oWorld.RemovePowerUp(iX, iY);
			}
		}

		//Beam player to pos
		//@ KR / 2014.07.09
		private void Beam(int iPlayer)
		{		
			int		iX, iY;
			int		iOpX = 0, iOpY = 0;
			Random	oRand = new Random();

			m_rgoPlayer[iPlayer == 0 ? 1 : 0].GetPos(ref iOpX, ref iOpY);
			do	//Generate random pos that is not occupied
			{
				iX = oRand.Next(Const.GRIDS_XAXIS);
				iY = oRand.Next(Const.GRIDS_YAXIS);			
			}
			while(iX == iOpX || iY == iOpY || !m_oWorld.PosFree(iX, iY));

			m_rgoPlayer[iPlayer].SetPos(iX, iY);
		}

//////////////////////////////////////////////////////////////////////////////////////////////////////
////////// STATS

		//Get player stats
		//@ KR / 2014.07.08
		public void GetStats(int iPlayer, ref int iMove, ref int iStaminaUsed, ref int iKicks)
		{
			iPlayer--;
			iMove			= m_rgoPlayer[iPlayer].GetMoveCount();
			iStaminaUsed	= m_rgoPlayer[iPlayer].GetStaminaUsedCount();
			iKicks			= m_rgoPlayer[iPlayer].GetKicksCount();
		}


//////////////////////////////////////////////////////////////////////////////////////////////////////
////////// GRAPHICS

		//Get player idle image
		//@ KR / 2014.07.08
		public Bitmap GetPlayerImage(int iPlayer)
		{
			return m_rgoPlayer[--iPlayer].GetImage();
		}
	}
}
